﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static LightCAD.Three.Constants;
using LightCAD.Three.OpenGL;

namespace LightCAD.Three
{

    public class WebGLPrograms : IDispose
    {
        public class Parameters
        {
            public Uniforms uniforms;
            public bool isWebGL2;
            public string shaderID;
            public string shaderName;
            public string vertexShader;
            public string fragmentShader;
            public JsObj<object> defines;
            public int customVertexShaderID;
            public int customFragmentShaderID;
            public bool isRawShaderMaterial;
            public string glslVersion;
            public string precision;
            public bool instancing;
            public bool instancingColor;
            public bool supportsVertexTextures;
            public int outputEncoding;
            public bool map;
            public int maps;
            public bool matcap;
            public bool envMap;
            public int envMapMode;
            public int? envMapCubeUVHeight;
            public bool lightMap;
            public bool aoMap;
            public bool emissiveMap;
            public bool bumpMap;
            public bool normalMap;
            public bool objectSpaceNormalMap;
            public bool tangentSpaceNormalMap;
            public bool decodeVideoTexture;
            public bool clearcoat;
            public bool clearcoatMap;
            public bool clearcoatRoughnessMap;
            public bool clearcoatNormalMap;
            public bool iridescence;
            public bool iridescenceMap;
            public bool iridescenceThicknessMap;
            public bool displacementMap;
            public bool roughnessMap;
            public bool metalnessMap;
            public bool specularMap;
            public bool specularIntensityMap;
            public bool specularColorMap;
            public bool opaque;
            public bool alphaMap;
            public bool alphaTest;
            public bool gradientMap;
            public bool sheen;
            public bool sheenColorMap;
            public bool sheenRoughnessMap;
            public bool transmission;
            public bool transmissionMap;
            public bool thicknessMap;
            public int? combine;
            public bool vertexTangents;
            public bool vertexColors;
            public bool vertexAlphas;
            public bool vertexUvs;
            public bool uvsVertexOnly;
            public bool fog;
            public bool useFog;
            public bool fogExp2;
            public bool flatShading;
            public bool sizeAttenuation;
            public bool logarithmicDepthBuffer;
            public bool skinning;
            public bool morphTargets;
            public bool morphNormals;
            public bool morphColors;
            public int morphTargetsCount;
            public int morphAttributeCount;
            public int morphTextureStride;
            public int numDirLights;
            public int numPointLights;
            public int numSpotLights;
            public int numSpotLightMaps;
            public int numRectAreaLights;
            public int numHemiLights;
            public int numDirLightShadows;
            public int numPointLightShadows;
            public int numSpotLightShadows;
            public int numSpotLightShadowsWithMaps;
            public int numClippingPlanes;
            public int numClipIntersection;
            public bool dithering;
            public bool shadowMapEnabled;
            public int shadowMapType;
            public int toneMapping;
            public bool useLegacyLights;
            public bool premultipliedAlpha;
            public bool doubleSided;
            public bool flipSided;
            public bool useDepthPacking;
            public object depthPacking;
            public string index0AttributeName;
            public object extensionDerivatives;
            public bool extensionFragDepth;
            public bool extensionDrawBuffers;
            public bool extensionShaderTextureLOD;
            public bool rendererExtensionFragDepth;
            public bool rendererExtensionDrawBuffers;
            public bool rendererExtensionShaderTextureLod;
            public string customProgramCacheKey;
            public bool normalMapObjectSpace = false;
            public bool normalMapTangentSpace = false;
            public string mapUv;
            public string alphaMapUv;
            public string lightMapUv;
            public string aoMapUv;
            public string bumpMapUv;
            public string normalMapUv;
            public string displacementMapUv;
            public string emissiveMapUv;
            public string metalnessMapUv;
            public string roughnessMapUv;
            public string clearcoatMapUv;
            public string clearcoatNormalMapUv;
            public string clearcoatRoughnessMapUv;
            public string iridescenceMapUv;
            public string iridescenceThicknessMapUv;
            public string sheenColorMapUv;
            public string sheenRoughnessMapUv;
            public string specularMapUv;
            public string specularColorMapUv;
            public string specularIntensityMapUv;
            public string transmissionMapUv;
            public string thicknessMapUv;
            public bool vertexUvs2 = false;
            public bool pointsUvs = false;
        }

        public WebGLRenderer renderer;
        public WebGLCubeMaps cubemaps;
        public WebGLCubeUVMaps cubeuvmaps;
        public WebGLExtensions extensions;
        public WebGLCapabilities capabilities;
        public WebGLBindingStates bindingStates;
        public WebGLClipping clipping;

        private Layers _programLayers;
        private WebGLShaderCache _customShaders;
        public JsArr<WebGLProgram> programs;
        private bool IS_WEBGL2;
        private bool logarithmicDepthBuffer;
        private bool SUPPORTS_VERTEX_TEXTURES;
        private string precision;

        private JsObj<string> shaderIDs = new JsObj<string>()
            {
                {"MeshDepthMaterial","depth"},
                {"MeshDistanceMaterial","distanceRGBA"},
                {"MeshNormalMaterial","normal"},
                {"MeshBasicMaterial","basic"},
                {"MeshLambertMaterial","lambert"},
                {"MeshPhongMaterial","phong"},
                {"MeshToonMaterial","toon"},
                {"MeshStandardMaterial","physical"},
                {"MeshPhysicalMaterial","physical"},
                {"MeshMatcapMaterial","matcap"},
                {"LineBasicMaterial","basic"},
                {"LineDashedMaterial","dashed"},
                {"PointsMaterial","points"},
                {"ShadowMaterial","shadow"},
                {"SpriteMaterial","sprite"}
            };

        public WebGLPrograms(WebGLRenderer renderer, WebGLCubeMaps cubemaps, WebGLCubeUVMaps cubeuvmaps, WebGLExtensions extensions, WebGLCapabilities capabilities, WebGLBindingStates bindingStates, WebGLClipping clipping)
        {
            this.renderer = renderer;
            this.cubemaps = cubemaps;
            this.cubeuvmaps = cubeuvmaps;
            this.extensions = extensions;
            this.capabilities = capabilities;
            this.bindingStates = bindingStates;
            this.clipping = clipping;

            this._programLayers = new Layers();
            this._customShaders = new WebGLShaderCache();
            this.programs = new JsArr<WebGLProgram>();
            this.IS_WEBGL2 = capabilities.isWebGL2;
            this.logarithmicDepthBuffer = capabilities.logarithmicDepthBuffer;
            this.SUPPORTS_VERTEX_TEXTURES = capabilities.vertexTextures;
            this.precision = capabilities.precision;
        }

        public string getChannel(int value)
        {
            if (value == 1) return "uv2";
            return "uv";
        }
        public Parameters getParameters(Material material, WebGLLights.State lights, JsArr<Light> shadows, Scene scene, IGeometry _object)
        {

            var fog = scene.fog;
            var geometry = _object.getGeometry();
            var environment = material is MeshStandardMaterial ? scene.environment : null;
            Texture envMap = null;
            if (material is MeshStandardMaterial)
            {
                envMap = cubeuvmaps.get((material.GetField("envMap") as Texture) ?? environment);
            }
            else
            {
                envMap = cubemaps.get((material.GetField("envMap") as Texture) ?? environment);
            }
            int? envMapCubeUVHeight = ((envMap != null)) && (envMap.mapping == CubeUVReflectionMapping) ? (int?)envMap.image.height : null;

            var shaderID = shaderIDs[material.type];

            // heuristics to create shader parameters according to lights in the scene
            // (not to blow over maxLights budget)

            if (material.precision != null)
            {

                precision = capabilities.getMaxPrecision(material.precision);

                if (precision != material.precision)
                {

                    console.warn("THREE.WebGLProgram.getParameters:", material.precision, "not supported, using", precision, "instead.");

                }

            }

            //

            var morphAttribute = geometry.morphAttributes.position ?? geometry.morphAttributes.normal ?? geometry.morphAttributes.color;
            var morphTargetsCount = (morphAttribute != null) ? morphAttribute.length : 0;

            int morphTextureStride = 0;

            if (geometry.morphAttributes.position != null) morphTextureStride = 1;
            if (geometry.morphAttributes.normal != null) morphTextureStride = 2;
            if (geometry.morphAttributes.color != null) morphTextureStride = 3;

            //

            string vertexShader, fragmentShader;
            int? customVertexShaderID = null, customFragmentShaderID = null;

            if (shaderID != null)
            {

                var shader = ShaderLib.GetShaderObj(shaderID);

                vertexShader = shader.vertexShader;
                fragmentShader = shader.fragmentShader;

            }
            else
            {
                var shaderMat = material as ShaderMaterial;
                vertexShader = shaderMat.vertexShader;
                fragmentShader = shaderMat.fragmentShader;

                _customShaders.update(shaderMat);

                customVertexShaderID = _customShaders.getVertexShaderID(shaderMat);
                customFragmentShaderID = _customShaders.getFragmentShaderID(shaderMat);

            }

            var currentRenderTarget = this.renderer.getRenderTarget();

            bool IS_INSTANCEDMESH = _object is InstancedMesh;

            bool HAS_MAP = material.map != null;
            bool HAS_MATCAP = material.matcap != null;
            bool HAS_ENVMAP = envMap != null;
            bool HAS_AOMAP = material.aoMap != null;
            bool HAS_LIGHTMAP = material.lightMap != null;
            bool HAS_BUMPMAP = material.bumpMap != null;
            bool HAS_NORMALMAP = material.normalMap != null;
            bool HAS_DISPLACEMENTMAP = material.displacementMap != null;
            bool HAS_EMISSIVEMAP = material.emissiveMap != null;

            bool HAS_METALNESSMAP = material.metalnessMap != null;
            bool HAS_ROUGHNESSMAP = material.roughnessMap != null;

            bool HAS_CLEARCOAT = material.clearcoat > 0;
            bool HAS_IRIDESCENCE = material.iridescence > 0;
            bool HAS_SHEEN = material.sheen > 0;
            bool HAS_TRANSMISSION = material.transmission > 0;

            bool HAS_CLEARCOATMAP = HAS_CLEARCOAT && material.clearcoatMap != null;
            bool HAS_CLEARCOAT_NORMALMAP = HAS_CLEARCOAT && material.clearcoatNormalMap != null;
            bool HAS_CLEARCOAT_ROUGHNESSMAP = HAS_CLEARCOAT && material.clearcoatRoughnessMap != null;

            bool HAS_IRIDESCENCEMAP = HAS_IRIDESCENCE && material.iridescenceMap != null;
            bool HAS_IRIDESCENCE_THICKNESSMAP = HAS_IRIDESCENCE && material.iridescenceThicknessMap != null;

            bool HAS_SHEEN_COLORMAP = HAS_SHEEN && material.sheenColorMap != null;
            bool HAS_SHEEN_ROUGHNESSMAP = HAS_SHEEN && material.sheenRoughnessMap != null;

            bool HAS_SPECULARMAP = material.specularMap != null;
            bool HAS_SPECULAR_COLORMAP = material.specularColorMap != null;
            bool HAS_SPECULAR_INTENSITYMAP = material.specularIntensityMap != null;

            bool HAS_TRANSMISSIONMAP = HAS_TRANSMISSION && material.transmissionMap != null;
            bool HAS_THICKNESSMAP = HAS_TRANSMISSION && material.thicknessMap != null;

            bool HAS_GRADIENTMAP = material.gradientMap != null;

            bool HAS_ALPHAMAP = material.alphaMap != null;

            bool HAS_ALPHATEST = material.alphaTest > 0;

            bool HAS_EXTENSIONS = (material as ShaderMaterial)?.extensions != null;

            bool HAS_ATTRIBUTE_UV2 = geometry.attributes.uv2 != null;

            var matExtensions = (material as ShaderMaterial)?.extensions;
            var parameters = new Parameters();
            parameters.isWebGL2 = IS_WEBGL2;
            parameters.shaderID = shaderID;
            parameters.shaderName = material.type;
            parameters.vertexShader = vertexShader;
            parameters.fragmentShader = fragmentShader;
            parameters.defines = material.defines;
            parameters.customVertexShaderID = customVertexShaderID ?? -1;
            parameters.customFragmentShaderID = customFragmentShaderID ?? -1;
            parameters.isRawShaderMaterial = material is RawShaderMaterial;
            parameters.glslVersion = material.args.glslVersion ?? "";
            parameters.precision = precision;
            parameters.instancing = IS_INSTANCEDMESH;
            parameters.instancingColor = IS_INSTANCEDMESH && (_object as InstancedMesh).instanceColor != null;

            parameters.supportsVertexTextures = SUPPORTS_VERTEX_TEXTURES;
            parameters.outputEncoding = (currentRenderTarget == null) ? renderer.outputEncoding : (currentRenderTarget.isXRRenderTarget ? currentRenderTarget.texture.encoding : LinearEncoding);

            parameters.map = HAS_MAP;
            parameters.matcap = HAS_MATCAP;
            parameters.envMap = HAS_ENVMAP;
            parameters.envMapMode = HAS_ENVMAP ? envMap.mapping : -1;
            parameters.envMapCubeUVHeight = envMapCubeUVHeight;
            parameters.aoMap = HAS_AOMAP;
            parameters.lightMap = HAS_LIGHTMAP;
            parameters.bumpMap = HAS_BUMPMAP;
            parameters.normalMap = HAS_NORMALMAP;
            parameters.displacementMap = SUPPORTS_VERTEX_TEXTURES && HAS_DISPLACEMENTMAP;
            parameters.emissiveMap = HAS_EMISSIVEMAP;

            parameters.normalMapObjectSpace = HAS_NORMALMAP && material.normalMapType == ObjectSpaceNormalMap;
            parameters.normalMapTangentSpace = HAS_NORMALMAP && material.normalMapType == TangentSpaceNormalMap;

            parameters.decodeVideoTexture = HAS_MAP && (material.map is VideoTexture) && (material.map.encoding == sRGBEncoding);

            parameters.metalnessMap = HAS_METALNESSMAP;
            parameters.roughnessMap = HAS_ROUGHNESSMAP;

            parameters.clearcoat = HAS_CLEARCOAT;
            parameters.clearcoatMap = HAS_CLEARCOATMAP;
            parameters.clearcoatNormalMap = HAS_CLEARCOAT_NORMALMAP;
            parameters.clearcoatRoughnessMap = HAS_CLEARCOAT_ROUGHNESSMAP;

            parameters.iridescence = HAS_IRIDESCENCE;
            parameters.iridescenceMap = HAS_IRIDESCENCEMAP;
            parameters.iridescenceThicknessMap = HAS_IRIDESCENCE_THICKNESSMAP;

            parameters.sheen = HAS_SHEEN;
            parameters.sheenColorMap = HAS_SHEEN_COLORMAP;
            parameters.sheenRoughnessMap = HAS_SHEEN_ROUGHNESSMAP;

            parameters.specularMap = HAS_SPECULARMAP;
            parameters.specularColorMap = HAS_SPECULAR_COLORMAP;
            parameters.specularIntensityMap = HAS_SPECULAR_INTENSITYMAP;

            parameters.transmission = HAS_TRANSMISSION;
            parameters.transmissionMap = HAS_TRANSMISSIONMAP;
            parameters.thicknessMap = HAS_THICKNESSMAP;

            parameters.gradientMap = HAS_GRADIENTMAP;
            parameters.opaque = material.transparent == false && material.blending == NormalBlending;
            parameters.alphaMap = HAS_ALPHAMAP;
            parameters.alphaTest = HAS_ALPHATEST;

            parameters.combine = material.combine;

            //

            parameters.mapUv = HAS_MAP ? getChannel(material.map.channel) : null;
            parameters.aoMapUv = HAS_AOMAP ? getChannel(material.aoMap.channel) : null;
            parameters.lightMapUv = HAS_LIGHTMAP ? getChannel(material.lightMap.channel) : null;
            parameters.bumpMapUv = HAS_BUMPMAP ? getChannel(material.bumpMap.channel) : null;
            parameters.normalMapUv = HAS_NORMALMAP ? getChannel(material.normalMap.channel) : null;
            parameters.displacementMapUv = HAS_DISPLACEMENTMAP ? getChannel(material.displacementMap.channel) : null;
            parameters.emissiveMapUv = HAS_EMISSIVEMAP ? getChannel(material.emissiveMap.channel) : null;

            parameters.metalnessMapUv = HAS_METALNESSMAP ? getChannel(material.metalnessMap.channel) : null;
            parameters.roughnessMapUv = HAS_ROUGHNESSMAP ? getChannel(material.roughnessMap.channel) : null;

            parameters.clearcoatMapUv = HAS_CLEARCOATMAP ? getChannel(material.clearcoatMap.channel) : null;
            parameters.clearcoatNormalMapUv = HAS_CLEARCOAT_NORMALMAP ? getChannel(material.clearcoatNormalMap.channel) : null;
            parameters.clearcoatRoughnessMapUv = HAS_CLEARCOAT_ROUGHNESSMAP ? getChannel(material.clearcoatRoughnessMap.channel) : null;

            parameters.iridescenceMapUv = HAS_IRIDESCENCEMAP ? getChannel(material.iridescenceMap.channel) : null;
            parameters.iridescenceThicknessMapUv = HAS_IRIDESCENCE_THICKNESSMAP ? getChannel(material.iridescenceThicknessMap.channel) : null;

            parameters.sheenColorMapUv = HAS_SHEEN_COLORMAP ? getChannel(material.sheenColorMap.channel) : null;
            parameters.sheenRoughnessMapUv = HAS_SHEEN_ROUGHNESSMAP ? getChannel(material.sheenRoughnessMap.channel) : null;

            parameters.specularMapUv = HAS_SPECULARMAP ? getChannel(material.specularMap.channel) : null;
            parameters.specularColorMapUv = HAS_SPECULAR_COLORMAP ? getChannel(material.specularColorMap.channel) : null;
            parameters.specularIntensityMapUv = HAS_SPECULAR_INTENSITYMAP ? getChannel(material.specularIntensityMap.channel) : null;

            parameters.transmissionMapUv = HAS_TRANSMISSIONMAP ? getChannel(material.transmissionMap.channel) : null;
            parameters.thicknessMapUv = HAS_THICKNESSMAP ? getChannel(material.thicknessMap.channel) : null;

            parameters.alphaMapUv = HAS_ALPHAMAP ? getChannel(material.alphaMap.channel) : null;

            //

            parameters.vertexTangents = HAS_NORMALMAP && geometry.attributes.tangent != null;
            parameters.vertexColors = material.vertexColors;
            parameters.vertexAlphas = material.vertexColors && geometry.attributes.color != null && geometry.attributes.color.itemSize == 4;
            parameters.vertexUvs2 = HAS_ATTRIBUTE_UV2;

            parameters.pointsUvs = _object is Points && geometry.attributes.uv != null && (HAS_MAP || HAS_ALPHAMAP);


            parameters.fog = fog != null;
            parameters.useFog = material.fog;
            parameters.fogExp2 = fog != null && fog is FogExp2;
            parameters.flatShading = material.flatShading;
            parameters.sizeAttenuation = material.sizeAttenuation;
            parameters.logarithmicDepthBuffer = logarithmicDepthBuffer;
            parameters.skinning = _object is SkinnedMesh;
            parameters.morphTargets = geometry.morphAttributes.position != null;
            parameters.morphNormals = geometry.morphAttributes.normal != null;
            parameters.morphColors = geometry.morphAttributes.color != null;
            parameters.morphTargetsCount = morphTargetsCount;
            parameters.morphTextureStride = morphTextureStride;
            parameters.numDirLights = lights.directional.length;
            parameters.numPointLights = lights.point.length;
            parameters.numSpotLights = lights.spot.length;
            parameters.numSpotLightMaps = lights.spotLightMap.length;
            parameters.numRectAreaLights = lights.rectArea.length;
            parameters.numHemiLights = lights.hemi.length;
            parameters.numDirLightShadows = lights.directionalShadowMap.length;
            parameters.numPointLightShadows = lights.pointShadowMap.length;
            parameters.numSpotLightShadows = lights.spotShadowMap.length;
            parameters.numSpotLightShadowsWithMaps = lights.numSpotLightShadowsWithMaps;
            parameters.numClippingPlanes = clipping.numPlanes;
            parameters.numClipIntersection = clipping.numIntersection;
            parameters.dithering = material.dithering;
            parameters.shadowMapEnabled = (renderer.shadowMap?.enabled) ?? false && shadows.length > 0;
            parameters.shadowMapType = renderer.shadowMap?.type ?? 0;
            parameters.toneMapping = material.toneMapped ? renderer.toneMapping : NoToneMapping;
            parameters.useLegacyLights = renderer.useLegacyLights;
            parameters.premultipliedAlpha = material.premultipliedAlpha;
            parameters.doubleSided = material.side == DoubleSide;
            parameters.flipSided = material.side == BackSide;
            parameters.useDepthPacking = material.depthPacking >= 0;
            parameters.depthPacking = material.depthPacking;
            parameters.index0AttributeName = material.args.index0AttributeName;
            parameters.extensionDerivatives = HAS_EXTENSIONS && matExtensions.derivatives;
            parameters.extensionFragDepth = HAS_EXTENSIONS && matExtensions.fragDepth;
            parameters.extensionDrawBuffers = HAS_EXTENSIONS && matExtensions.drawBuffers;
            parameters.extensionShaderTextureLOD = HAS_EXTENSIONS && matExtensions.shaderTextureLOD;
            parameters.rendererExtensionFragDepth = IS_WEBGL2 || extensions.has("EXT_frag_depth");
            parameters.rendererExtensionDrawBuffers = IS_WEBGL2 || extensions.has("WEBGL_draw_buffers");
            parameters.rendererExtensionShaderTextureLod = IS_WEBGL2 || extensions.has("EXT_shader_texture_lod");
            parameters.customProgramCacheKey = material.customProgramCacheKey();

            return parameters;

        }


        public string getProgramCacheKey(Parameters parameters)
        {

            var array = new JsArr<object>();

            if (parameters.shaderID != null)
            {

                array.push(parameters.shaderID);

            }
            else
            {

                array.push(parameters.customVertexShaderID);
                array.push(parameters.customFragmentShaderID);

            }

            if (parameters.defines != null)
            {

                foreach (string name in parameters.defines.Keys)
                {

                    array.push(name);
                    array.push(parameters.defines[name]);

                }

            }

            if (parameters.isRawShaderMaterial == false)
            {

                getProgramCacheKeyParameters(array, parameters);
                getProgramCacheKeyBooleans(array, parameters);
                array.push(renderer.outputEncoding);

            }

            array.push(parameters.customProgramCacheKey);

            return array.join(",");

        }

        private void getProgramCacheKeyParameters(JsArr<object> array, Parameters parameters)
        {
            array.push(parameters.precision);
            array.push(parameters.outputEncoding);
            array.push(parameters.envMapMode);
            array.push(parameters.envMapCubeUVHeight);
            array.push(parameters.mapUv);
            array.push(parameters.alphaMapUv);
            array.push(parameters.lightMapUv);
            array.push(parameters.aoMapUv);
            array.push(parameters.bumpMapUv);
            array.push(parameters.normalMapUv);
            array.push(parameters.displacementMapUv);
            array.push(parameters.emissiveMapUv);
            array.push(parameters.metalnessMapUv);
            array.push(parameters.roughnessMapUv);
            array.push(parameters.clearcoatMapUv);
            array.push(parameters.clearcoatNormalMapUv);
            array.push(parameters.clearcoatRoughnessMapUv);
            array.push(parameters.iridescenceMapUv);
            array.push(parameters.iridescenceThicknessMapUv);
            array.push(parameters.sheenColorMapUv);
            array.push(parameters.sheenRoughnessMapUv);
            array.push(parameters.specularMapUv);
            array.push(parameters.specularColorMapUv);
            array.push(parameters.specularIntensityMapUv);
            array.push(parameters.transmissionMapUv);
            array.push(parameters.thicknessMapUv);
            array.push(parameters.combine);
            array.push(parameters.fogExp2);
            array.push(parameters.sizeAttenuation);
            array.push(parameters.morphTargetsCount);
            array.push(parameters.morphAttributeCount);
            array.push(parameters.numDirLights);
            array.push(parameters.numPointLights);
            array.push(parameters.numSpotLights);
            array.push(parameters.numSpotLightMaps);
            array.push(parameters.numHemiLights);
            array.push(parameters.numRectAreaLights);
            array.push(parameters.numDirLightShadows);
            array.push(parameters.numPointLightShadows);
            array.push(parameters.numSpotLightShadows);
            array.push(parameters.numSpotLightShadowsWithMaps);
            array.push(parameters.shadowMapType);
            array.push(parameters.toneMapping);
            array.push(parameters.numClippingPlanes);
            array.push(parameters.numClipIntersection);
            array.push(parameters.depthPacking);
        }

        private void getProgramCacheKeyBooleans(JsArr<object> array, Parameters parameters)
        {
            _programLayers.disableAll();

            if (parameters.isWebGL2)
                _programLayers.enable(0);
            if (parameters.supportsVertexTextures)
                _programLayers.enable(1);
            if (parameters.instancing)
                _programLayers.enable(2);
            if (parameters.instancingColor)
                _programLayers.enable(3);
            if (parameters.matcap)
                _programLayers.enable(4);
            if (parameters.envMap)
                _programLayers.enable(5);
            if (parameters.normalMapObjectSpace)
                _programLayers.enable(6);
            if (parameters.normalMapTangentSpace)
                _programLayers.enable(7);
            if (parameters.clearcoat)
                _programLayers.enable(8);
            if (parameters.iridescence)
                _programLayers.enable(9);
            if (parameters.alphaTest)
                _programLayers.enable(10);
            if (parameters.vertexColors)
                _programLayers.enable(11);
            if (parameters.vertexAlphas)
                _programLayers.enable(12);
            if (parameters.vertexUvs2)
                _programLayers.enable(13);
            if (parameters.vertexTangents)
                _programLayers.enable(14);


            array.push(_programLayers.mask);
            _programLayers.disableAll();

            if (parameters.fog)
                _programLayers.enable(0);
            if (parameters.useFog)
                _programLayers.enable(1);
            if (parameters.flatShading)
                _programLayers.enable(2);
            if (parameters.logarithmicDepthBuffer)
                _programLayers.enable(3);
            if (parameters.skinning)
                _programLayers.enable(4);
            if (parameters.morphTargets)
                _programLayers.enable(5);
            if (parameters.morphNormals)
                _programLayers.enable(6);
            if (parameters.morphColors)
                _programLayers.enable(7);
            if (parameters.premultipliedAlpha)
                _programLayers.enable(8);
            if (parameters.shadowMapEnabled)
                _programLayers.enable(9);
            if (parameters.useLegacyLights)
                _programLayers.enable(10);
            if (parameters.doubleSided)
                _programLayers.enable(11);
            if (parameters.flipSided)
                _programLayers.enable(12);
            if (parameters.useDepthPacking)
                _programLayers.enable(13);
            if (parameters.dithering)
                _programLayers.enable(14);
            if (parameters.transmission)
                _programLayers.enable(15);
            if (parameters.sheen)
                _programLayers.enable(16);
            if (parameters.decodeVideoTexture)
                _programLayers.enable(17);
            if (parameters.opaque)
                _programLayers.enable(18);
            if (parameters.pointsUvs)
                _programLayers.enable(19);

            array.push(_programLayers.mask);

        }

        public Uniforms getUniforms(Material material)
        {
            var shaderID = shaderIDs[material.type];
            Uniforms uniforms;

            if (shaderID != null)
            {

                var shader = ShaderLib.GetShaderObj(shaderID);
                uniforms = UniformsUtils.cloneUniforms(shader.uniforms);

            }
            else
            {

                uniforms = material.args.uniforms;

            }

            return uniforms;

        }

        public WebGLProgram acquireProgram(Parameters parameters, string cacheKey)
        {

            WebGLProgram program = null;

            // Check if code has been already compiled
            for (int p = 0, pl = programs.length; p < pl; p++)
            {

                var preexistingProgram = programs[p];

                if (preexistingProgram.cacheKey == cacheKey)
                {

                    program = preexistingProgram;
                    ++program.usedTimes;

                    break;

                }

            }

            if (program == null)
            {

                program = new WebGLProgram(renderer, cacheKey, parameters, bindingStates);
                programs.push(program);

            }

            return program;

        }

        public void releaseProgram(WebGLProgram program)
        {

            if (--program.usedTimes == 0)
            {

                // Remove from unordered set
                int i = programs.indexOf(program);
                programs[i] = programs[programs.length - 1];
                programs.pop();

                // Free WebGL resources
                program.destroy();

            }

        }

        public void releaseShaderCache(ShaderMaterial material)
        {

            _customShaders.remove(material);

        }

        public void dispose()
        {
            _customShaders.dispose();
        }
    }
}
